unit uUniverse;

interface

uses uDefs, uAstro, uFleet, uEmpire, uStarLane,
     cuVector, cuSimpleINI, cuDebug, cuSimpleArrays, cuZoom,

    Types, Classes, Contnrs;

type
  TUniverse = class( TObject )
  public
    uniCurrentWeek       : integer;
    uniStartYear         : integer;

    uniMinPlanets        : integer; // Minimum planets allowed in a system.
    uniMaxPlanets        : integer; // Maximum planets allowed in a system.
    uniMaxOrbits         : integer; // Planets can vary in distance to a star, this value sets the maximum orbit slots.
    uniMaxSolarSystems   : integer; // Total number of solarsystems.
    uniMinStarLanes      : integer;
    uniMaxStarLanes      : integer;
    uniBlackSpace        : integer; // Virtual space between systems, the larger the BlackSpace, the longer to travel between systems.

    uniGalaxyStyle       : string;

    uniResWidth          : integer; // Screen width
    uniResHeight         : integer; // Screen height
    uniFullscreen        : boolean;
    uniBaseFPS           : integer;
    uniTextureWidth      : integer; // Galaxy Map Texture size
    uniTextureHeight     : integer;
    uniWhiteChance       : integer; // Chance for a solarsystem to be placed in a White Pixel
    uniRedChance         : integer; // Chance for a solarsystem to be placed in a Red Pixel
    uniBlackChance       : integer; // Chance for a solarsystem to be placed in a Black Pixel

    uniAIPlayers         : integer; // Total AI Players. this could be set in game too
    uniEmpireDead        : boolean;

    uniPopulation        : integer; // Total Universe Population
    uniMaxPopulation     : integer; // Total Universe Max Population

    uniAstro             : TObjectList; // List of Universe Astral bodies
    uniStarLane          : TObjectList; // List of Universe Starlanes
    uniEmpire            : TObjectList;
    uniFleet             : TObjectList;
    uniShip              : TObjectList;

    uniStarList          : TIntegerArray; // Holds List of astro ID's that are stars, just a quick way to do solarsystems.

    uniZoom              : TZoom;
    uniStart             : TVector2D; //Start location.

    m_updateStarMap        : boolean; // Once called it will update the Star Map Texture, This saves FPS rather than updating every render.
    m_updateColonyMap      : boolean;
    m_updateFleetMap       : boolean;
    m_updateAllMaps        : boolean;

    SimpleINI              : TSimpleINI;

    constructor Create();
    destructor Destroy(); override;
    procedure resetUniverse();
    procedure New();

    function addAstro():integer;
    function addStar(newSolarID:integer;nLocation:TVector2D):integer; // Position of a star for a solarsystem.
    function addPlanet(newPlanetID,newOrbitID,newOrbitPath:integer):integer;
    procedure createSolarSystem(newSolarID:integer;nLocation:TVector2D);
    procedure createGalaxy(nFilename:string);
    function SystemNameGenerator():string; // Still need a check for double names...
    function findOrbittingAstros(newOrbitParentID:integer):TIntegerArray;
    function findOrbittingFleets(theAstroID:integer):TIntegerArray;

    procedure createStarLanes1();
    procedure decreaseStarlanes();
    procedure increaseStarLanes();

    procedure createUniverse();

    //Empire
    procedure createEmpires();
    procedure createEmpire(newEmpireID:integer;newRulerName:string);
    procedure tickerEmpires();

    //Colony
    function takeAstro(theAstroID,newEmpireID:integer):integer;
    function leaveAstro(theAstroID:integer):integer;
    procedure capturePlanet(newAstroID,newEmpireID:integer);
    procedure captureSolarSystem(newSolarID,newEmpireID:integer);
    procedure tickerAstro();

    //Fleet
    function createFleet(newAstroID,theEmpireID:integer):integer;
    function fleetOnRoute(theAstroID,theEmpireID:integer):boolean;
    function findTarget(theEmpireID,theFleetID:integer):integer;
    procedure tickerFleetMove();
    procedure tickerFleetCombat();
    procedure tickerFleetFight(fleetsInOrbit:TIntegerArray);
    procedure tickerFleets();

    function getAstro(theAstroID:integer):TAstro;
    function getStarLane(theStarLaneID:integer):TStarLane;
    function getEmpire(theEmpireID:integer):TEmpire;
    function getFleet(theFleetID:integer):TFleet;

    procedure Ticker();
    procedure exportInfo();

    procedure updateStarMap(theValue:boolean=TRUE);
    procedure updateColonyMap(theValue:boolean=TRUE);
    procedure updateFleetMap(theValue:boolean=TRUE);
    procedure updateAllMaps(theValue:boolean=TRUE);
  end;

implementation

uses SysUtils, cuFuncs, Graphics, Dialogs, cuSorting;

constructor TUniverse.Create();
begin
  inherited;

  uniAstro    := TObjectList.Create();
  uniStarlane := TObjectList.Create();
  uniEmpire   := TObjectList.Create();
  uniFleet    := TObjectList.Create();
  uniShip     := TObjectList.Create();

  uniStarList := TIntegerArray.Create('TStarList');

  uniZoom    := TZoom.Create();
  uniStart   := TVector2D.Create();

  resetUniverse();
end;
destructor TUniverse.Destroy();
begin
  resetUniverse();

  FreeAndNil( uniAstro    );
  FreeAndNil( uniStarlane );
  FreeAndNil( uniEmpire   );
  FreeAndNil( uniFleet    );
  FreeAndNil( uniShip     );

  FreeAndNil( uniStarList );

  FreeAndNil( uniZoom     );

  inherited;
end;
procedure TUniverse.resetUniverse();
begin
  uniAstro.Clear();
  uniStarlane.Clear();
  uniEmpire.Clear();
  uniFleet.Clear();
  uniShip.Clear();

  uniStarList.ClearArray('resetUniverse');
end;
procedure TUniverse.New();
begin
  DevConsole.Print(LOG_DEBUG1, '[TUniverse] New');

  resetUniverse();

  uniCurrentWeek      := 0;
  uniStartYear        := 2300+Random(500);

  uniMinPlanets       := SimpleINI.iFind('iMinPlanets');
  uniMaxPlanets       := SimpleINI.iFind('iMaxPlanets');
  uniMaxOrbits        := SimpleINI.iFind('iMaxOrbits');
  uniMaxSolarSystems  := SimpleINI.iFind('iMaxSolarSystems');

  uniBlackSpace       := SimpleINI.iFind('iBlackSpace');

  uniGalaxyStyle      := SimpleINI.sFind('sGalaxyType');

  uniMinStarLanes     := SimpleINI.iFind('iMinStarLanes');
  uniMaxStarLanes     := SimpleINI.iFind('iMaxStarLanes');

  uniAIPlayers        := SimpleINI.iFind('iAIPlayers');
  uniEmpireDead       := SimpleINI.bFind('bEmpireDead');

  uniZoom.SetZoom( SimpleINI.rFind('rZoom'),
                   SimpleINI.rFind('rZoomStep'),
                   SimpleINI.rFind('rZoomMin'),
                   SimpleINI.rFind('rZoomMax'));

  uniResWidth          := SimpleINI.iFind('iScreenWidth'); // These are now in the settings.ini file and changing those will affect this.
  uniResHeight         := SimpleINI.iFind('iScreenHeight');
  uniFullscreen        := SimpleINI.bFind('bFullscreen');
  uniBaseFPS           := SimpleINI.iFind('iBaseFPS');
  uniTextureWidth      := SimpleINI.iFind('iTextureWidth');
  uniTextureHeight     := SimpleINI.iFind('iTextureHeight');
  uniWhiteChance       := SimpleINI.iFind('iWhiteChance');
  uniRedChance         := SimpleINI.iFind('iRedChance');
  uniBlackChance       := SimpleINI.iFind('iBlackChance');

  uniPopulation    := 0;
  uniMaxPopulation := 0;

  createUniverse();

  uniStart.setVector2D( round(uniResWidth/2)*uniBlackSpace, round(uniResHeight/2)*uniBlackSpace );// Centers the universe on screen

  updateStarMap();
  updateColonyMap();
  updateFleetMap();
  updateAllMaps();
end;

//=============================
//=========== Finding Things
//=============================
function TUniverse.findOrbittingAstros( newOrbitParentID:integer ):TIntegerArray;
var A : integer;
begin
  result := TIntegerArray.Create('[findOrbittingAstros]');

  for A := 0 to uniAstro.Count-1 do
  begin
    if (getAstro( A ).astroOrbitID = newOrbitParentID) then
      result.addItem( A );
  end;
end;
function TUniverse.findOrbittingFleets( theAstroID:integer ):TIntegerArray;
var F : integer;
begin
  result := TIntegerArray.Create('[findOrbittingFleets]');

  for F := 0 to uniFleet.Count-1 do
  begin
    with getFleet( F ) do
    begin
      if (not fleetActive) then continue;

      if (fleetInOrbit(theAstroID) ) then
        result.addItem( F );
    end;
  end;
end;

//=============================
//==    Astral Bodies
//=============================
function TUniverse.addAstro():integer;
begin
  result := uniAstro.Count;

  uniAstro.Add( TAstro.Create() );
end;
function TUniverse.addStar(newSolarID:integer;nLocation:TVector2D):integer;
var rand,S1,S2,S3 : integer;
begin
  result := addAstro();

//== Solar - Simple storage for stars only.
  uniStarList.addItem( result );
//==
  DevConsole.Print(LOG_DEBUG1, '[TUniverse] addStar - Adding star #'+IntToStr(result)+' from SolarSystem #'+IntToStr(newSolarID)+' at Vector '+nLocation.ToXY());

  with getAstro( result ) do
  begin
    astroEmpireID      := -1;

    astroOrbitID       := -1; // Orbits nothing.

    astroName          := SystemNameGenerator();
    astroCanBeCaptured := FALSE;
    astroType          := objectSTAR;

    rand  := Random(100);
    case Random(3) of
      0: astroClimate := climateYELLOWSTAR;
      1: astroClimate := climateREDSTAR;
      2: astroClimate := climateBLUESTAR;
    end;

    astroMineral         := mineralNONE;
    astroGravity         := gravityHIGH;
    astroSize            := 10;           // I could vary the Size of the star, which in turn varies the size of the planets orbitting.
    astroDegreesPerWeek  := 0;
    astroOrbitDistance   := 0;

    astroLocation.setVector2D( nLocation.X, nLocation.Y );

    astroHealth        := 100;
    astroMaxHealth     := 100;
  end;
end;
function TUniverse.addPlanet(newPlanetID,newOrbitID,newOrbitPath:integer):integer;
var theStarType : enumClimate; chanceCLIMATE : TIntegerArray; rand : integer;
begin
  result := addAstro(); // Increase array size.

  DevConsole.Print(LOG_DEBUG1, '[TUniverse] addPlanet - Adding planet '+IntToStr(newPlanetID)+' #'+IntToStr(result)+', orbitting around star #'+IntToStr(newOrbitID)+' on orbit path #'+IntToStr(newOrbitPath));

  with getAstro( result ) do
  begin
    astroEmpireID      := -1;
    astroOrbitID       := newOrbitID;

    theStarType        := getAstro( astroOrbitID ).astroClimate;
    astroName          := getAstro( astroOrbitID ).astroName+' '+IntToRoman( newPlanetID );
    
    astroCanBeCaptured := TRUE;
    astroType          := objectPLANET;

    astroHealth        := 100;
    astroMaxHealth     := 100;

    chanceCLIMATE := TIntegerArray.Create('chanceCLIMATE');

    case theStarType of
      climateREDSTAR    : chanceClimate.setArrayValues('25 25 10 3 3');
      climateYELLOWSTAR : chanceClimate.setArrayValues('3 15 30 15 3');
      climateBLUESTAR   : chanceClimate.setArrayValues('3 3 10 25 30');
    end;

    if (Random( chanceClimate.addedValue ) < chanceCLIMATE.getItem(0) ) then astroClimate := climateICE    else
    if (Random( chanceClimate.addedValue ) < chanceCLIMATE.getItem(1) ) then astroClimate := climateOCEAN  else
    if (Random( chanceClimate.addedValue ) < chanceCLIMATE.getItem(2) ) then astroClimate := climateGAIA   else
    if (Random( chanceClimate.addedValue ) < chanceCLIMATE.getItem(3) ) then astroClimate := climateGAS    else
    {if (Random( chanceClimate.addedValue ) < chanceCLIMATE.items[4] ) then} astroClimate := climateDESERT;

    astroSize          := Random(10)+1;

    astroDegreesPerWeek  := (1+((uniMaxOrbits-newOrbitPath)))/astroSize; // Planets closer to the star orbit faster, but bigger objects orbit slower
    astroInitialOrbit    := Random(360);
    astroOrbitDistance   := 500*((newOrbitPath+1)*2);

    case astroSize of
      1..3  : astroGravity := gravityLOW;
      4..7  : astroGravity := gravityNORMAL;
      8..10 : astroGravity := gravityHIGH;
    end;

    astroPopulation      := 0;
    astroMaxPopulation   := 100*astroSize;

    case astroClimate of
      climateICE    : begin astroGrowth := 1; astroMineral := mineralRICH;     DevConsole.Print(LOG_DEBUG2, '[TUniverse] addPlanet - This ICE planet has: Growth is +1, Mineral Content is RICH'); end;
      climateOCEAN  : begin astroGrowth := 2; astroMineral := mineralABUNDENT; DevConsole.Print(LOG_DEBUG2, '[TUniverse] addPlanet - This OCEAN planet has: Growth is +2, Mineral Content is ABUNDENT'); end;
      climateGAIA   : begin astroGrowth := 4; astroMineral := mineralPOOR;     DevConsole.Print(LOG_DEBUG2, '[TUniverse] addPlanet - This GAIA planet has: Growth is +4, Mineral Content is POOR'); end;
      climateGAS    : begin astroGrowth := 2; astroMineral := mineralABUNDENT; DevConsole.Print(LOG_DEBUG2, '[TUniverse] addPlanet - This GAS planet has: Growth is +2, Mineral Content is ABUNDENT'); end;
      climateDESERT : begin astroGrowth := 1; astroMineral := mineralRICH;     DevConsole.Print(LOG_DEBUG2, '[TUniverse] addPlanet - This DESERT planet has: Growth is +1, Mineral Content is RICH'); end;
    end;

    astroRevenue         := 0;
    astroSpending        := 0;

    astroLocation.setVector2D( 0, astroOrbitDistance );

    OrbitRotation2DFromZero( uniCurrentWeek );

    FreeAndNil( chanceCLIMATE );
  end;
end;
procedure TUniverse.createSolarSystem(newSolarID:integer;nLocation:TVector2D);
var P,Planets,orbitStarID,newOrbitPath,newOrbitID:integer; orbitList:TIntegerArray;
begin
  DevConsole.Print(LOG_DEBUG1, '[TUniverse] createSolarSystem - Creation of StarSystem #'+IntToStr(newSolarID));

//== Star
  orbitStarID := addStar( newSolarID, nLocation ); // The main part of the solar system
//== Planet
  Planets := (uniMinPlanets + random((uniMaxPlanets+1) - uniMinPlanets));

  orbitList   := TIntegerArray.Create('orbitList'); // Get ready for selecting orbits.
  orbitList.fillArray( 0, uniMaxOrbits );

  for P:= 0 to Planets-1 do //-1 due to 0..4 get it?
  begin
    try
      newOrbitID   := Random( orbitList.Count );
      newOrbitPath := orbitList.getItem( newOrbitID );
      DevConsole.Print(LOG_DEBUG3, '[TUniverse] createSolarSystem - OrbitIndex #'+IntToStr(newOrbitID)+', OrbitPath #'+IntToStr(newOrbitPath)+', Count #'+IntToStr(orbitList.Count));
      addPlanet(P, orbitStarID, newOrbitPath ); // updates uniCurrentAstroID to planetID

      orbitList.DeleteByValue( newOrbitID ); //dma
    except
      DevConsole.saveLog();
    end;
  end;

  FreeAndNil( orbitList );
end;
procedure TUniverse.createGalaxy(nFilename:string);
var S,X,Y : integer;
  PlaceFound : boolean;
  Chance : integer;
  PreImage,PostImage:TBitmap;
  aLocation : TVector2D;
begin
  DevConsole.Print(LOG_DEBUG1, '[TUniverse] createGalaxy()');

  nFilename := GetEXEPath+'images\galaxy\'+nFilename;

  if (not FileExists(nFilename)) then
  begin
    DevConsole.Print(LOG_CRITICAL, 'File does Not Exist: '+nFilename);
    exit;
  end;
  PreImage := Jpg2Bmp( nFilename, uniTextureWidth, uniTextureHeight );

  if (not FileExists( GetEXEPath+'images\galaxy\blank.jpg' )) then
  begin
    DevConsole.Print(LOG_CRITICAL, 'File does Not Exist: '+GetEXEPath+'images\galaxy\blank.jpg');
    exit;
  end;
  PostImage := Jpg2Bmp( GetEXEPath+'images\galaxy\blank.jpg', uniTextureWidth, uniTextureHeight );

  aLocation := TVector2D.Create();

  for S := 0 to uniMaxSolarSystems-1 do
  begin
    PlaceFound := FALSE;
    while (PlaceFound = FALSE) do
    begin
      X := Random(PreImage.Width);
      Y := Random(PreImage.Height);

      if (PostImage.Canvas.Pixels[X,Y] = clWhite) then Continue; //Already a star at that location.

      Case PreImage.Canvas.Pixels[X,Y] of
        clBlack : Chance := uniBlackChance;
        clWhite : Chance := uniWhiteChance;
        else      Chance := uniRedChance;
      end;

      if Random(1000)+1  <= Chance then
      begin
        aLocation.setVector2D( X ,Y ); // Setting BlackSpace here to increase distances between systems.
        aLocation.resizeToResolution2D( uniTextureWidth, uniTextureHeight, uniResWidth, uniResHeight);
        aLocation.setVector2D( aLocation.X*uniBlackSpace, aLocation.Y*uniBlackSpace );

        createSolarSystem( S, aLocation );
        PostImage.Canvas.Pixels[ X,Y ] := clWhite;

        PlaceFound := TRUE;
      end;
    end;
  end;

  PostImage.SaveToFile(GetEXEPath+'postimage.jpg');

  createStarLanes1();

  FreeAndNil( aLocation );
  FreeAndNil( PreImage );
  FreeAndNil( PostImage );
end;
function TUniverse.SystemNameGenerator():string;
// XX-XXX such as F7-XZH (Base this some how on coords)
// Uses hex values 0-9 a-z but letters are first.
// [Quaduant][Sector]-[System] - To use this I would need to fix the positions of the systems. no using Images to create the universe.
//NOTE: Not using numbers yet and no double name check either.
var I : integer; nstr : string;
begin
  nStr := '';
  for I := 0 to 4 do
  begin
    if (I = 2) then nStr := nStr + '-';

    nStr:= nStr+IntToLetter(Random(26));
  end;
  result := nStr;
end;

//=============================
//========== Colonise Astros
//=============================
function TUniverse.takeAstro(theAstroID,newEmpireID:integer):integer;
begin
  DevConsole.Print(LOG_DEBUG2, '[TUniverse] takeAstro - '+IntToAlpha(newEmpireID)+' ('+IntToStr(newEmpireID)+') has taken astro '+getAstro(theAstroID).astroName+' ('+IntToStr(theAstroID)+')');

  result := newEmpireID;

  getEmpire( newEmpireID ).linkAstro( theAstroID ); // Creates a new colony link for the new empire.

  getAstro( theAstroID ).linkEmpire( newEmpireID ); // sets link from colony to new empire.

  getAstro( theAstroID ).setPopulation( 0 );

  updateColonyMap();
end;
function TUniverse.leaveAstro(theAstroID:integer):integer;
//just removes links to colony and prehaps leaves it intact
begin
  //Find old empire id
  result := getAstro( theAstroID ).astroEmpireID;

  DevConsole.Print(LOG_DEBUG2, '[TUniverse] leaveAstro - Empire '+IntToAlpha(result)+' ('+IntToStr(result)+') has fled the planet '+getAstro( theAstroID ).astroName+' ('+IntToStr(theAstroID)+')');

  getEmpire( result ).unlinkAstro( theAstroID );

  updateColonyMap();
end;
procedure TUniverse.capturePlanet(newAstroID,newEmpireID:integer);
begin
  DevConsole.Print(LOG_DEBUG2, '[TUniverse] capturePlanet - Empire '+IntToAlpha(newEmpireID)+' ('+IntToStr(newEmpireID)+') has captured planet '+getAstro(newAstroID).astroName+' ('+IntToStr(newAstroID)+')');

  with getAstro( newAstroID ) do
    if (isPlanet) then
    begin
      if (astroEmpireID > -1) then // Only leave the planet if an empire already has control.
        leaveAstro( newAstroID );

      takeAstro( newAstroID, newEmpireID );
    end;
end;
procedure TUniverse.captureSolarSystem(newSolarID,newEmpireID:integer);
// Will capture the specific solar system. Although ownership of a solarsystem is based on majority planet holder of the system.
var P : integer; systemAstros : TIntegerArray;
begin
  DevConsole.Print(LOG_DEBUG1, '[TUniverse] captureSolarSystem - '+IntToAlpha(newEmpireID)+' ('+IntToStr(newEmpireID)+') has captured system '+getAstro(newSolarID).astroName+' ('+IntToStr(newSolarID)+')');

  systemAstros := findOrbittingAstros( newSolarID );

  for P := 0 to systemAstros.Count-1 do
    capturePlanet( systemAstros.getItem(P) , newEmpireID ); // Create a colony on the first planet in the system. This should be used in the AI phase and not in the empire creation phase.

  getAstro( newSolarID ).astroEmpireID := newEmpireID; //Setting the Stars Empire ID will mean the system belongs to that player.

  updateColonyMap();
end;
procedure TUniverse.tickerAstro();
var A : integer;
begin
  uniPopulation    := 0;
  uniMaxPopulation := 0;

  for A := 0 to uniStarList.Count-1 do
  begin
    with getAstro( uniStarList.getItem(A) ) do //Reset Stars Population storage values
    begin
      astroPopulation    := 0;
      astroMaxPopulation := 0;
      astroGrowth        := 0;
    end;
  end;

  for A := 0 to uniAstro.Count-1 do
  begin
    with getAstro( A ) do
    begin
      if (isPlanet() ) then
        OrbitRotation2DFromZero(uniCurrentWeek);

      if (astroEmpireID > -1) then
      begin //Continue;
        tickerPopulation();

        //Add to Empire
        Inc( getEmpire(astroEmpireID).empirePopulation     , astroPopulation );
        Inc( getEmpire(astroEmpireID).empireMaxPopulation  , astroMaxPopulation );
        Inc( getEmpire(astroEmpireID).empireTotalGrowth    , astroGrowth );
      end;

      //Add to Universe
      Inc( uniPopulation     , astroPopulation    );
      Inc( uniMaxPopulation  , astroMaxPopulation );

      //Add to Star
      if (astroOrbitID <> -1) then
      begin
        Inc( getAstro(astroOrbitID).astroPopulation     , astroPopulation    );
        Inc( getAstro(astroOrbitID).astroMaxPopulation  , astroMaxPopulation );
        Inc( getAstro(astroOrbitID).astroGrowth         , astroGrowth        );
      end;
    end;
  end;
end;

//=============================
//========== Universe
//=============================
procedure TUniverse.createStarLanes1();
var A,B,C:integer; RouteFound:boolean; SortList : TSortList;
begin
  DevConsole.Print(LOG_DEBUG1, '[TUniverse] createStarLanes() '+IntToStr(uniStarList.Count));

  SortList := TSortList.Create();

  SortList.setArray( uniStarList.Count );
//  SortList.Clear();
  for A := 0 to uniStarList.Count-1 do
  begin
    DevConsole.Print(LOG_DEBUG3, '[TUniverse] Add Values to Sort Array');

    for B := 0 to uniStarList.Count-1 do
    begin
      SortList.setValueF(B, B, getAstro(uniStarList.getItem(A)).astroLocation.getDistance2D( getAstro(uniStarList.getItem(B)).astroLocation.X, getAstro(uniStarList.getItem(B)).astroLocation.Y) );
    end;

    DevConsole.Print(LOG_DEBUG3, '[TUniverse] Sorting Floats()');
    SortList.ShellSortFloat();

    DevConsole.Print(LOG_DEBUG3, '[TUniverse] Select Closest Object');

    for C := 0 to uniMaxStarLanes do
    begin
      B := SortList.Items[C+1].Index; // 0 = Self, 1 = Closest Astro
      uniStarLane.Add( TStarlane.Create() );
      getStarLane( uniStarLane.Count-1 ).setStarLane( uniStarList.getItem(A), uniStarList.getItem(B) );
    end;

    DevConsole.Print(LOG_DEBUG2, '[TUniverse] Creating starLane from '+IntToStr(getStarLane( A ).startPoint)+' -> '+IntToStr(getStarLane( A ).endPoint)+' : '+FormatFloat('0.00', SortList.Items[1].ValueF));
//    DevConsole.Print(LOG_DEBUG2, '[TUniverse] Creating starLane from '+IntToStr(A)+' -> '+IntToStr(B)+' : '+FormatFloat('0.00', SortList.Items[1].ValueF));
  end;

  FreeAndNil( SortList );
end;
procedure TUniverse.decreaseStarlanes();
begin
  if (uniMaxStarLanes > 2) then
    Dec(uniMaxStarLanes);
end;
procedure TUniverse.increaseStarLanes();
begin
  Inc(uniMaxStarLanes);
end;

procedure TUniverse.createUniverse();
begin
  DevConsole.Print(LOG_DEBUG1, '[TUniverse] createUniverse()');

  createGalaxy( uniGalaxyStyle ); // Create universe based on image based map.

  createEmpires();

//  DevConsole.Print(LOG_DEBUG2, '[TUniverse] createUniverse - Star List '+uniStarList.exportValues());
end;

//=============================
//========== Empire
//=============================
procedure TUniverse.createEmpires();
var E : integer;
begin
  DevConsole.Print(LOG_DEBUG1, '[TUniverse] createEmpires() - AI: '+IntToStr(uniAIPlayers));

  for E := 0 to uniAIPlayers - 1 do
    createEmpire( E, IntToAlpha(E) );
end;
procedure TUniverse.createEmpire(newEmpireID:integer;newRulerName:string);
var theStarID, theEmpireID : integer;
begin
//  if (newEmpireID = -1) then newEmpireID := uniEmpire.Count;
//  if (newRulerName = '') then newRulerName := IntToAlpha( newEmpireID );

  DevConsole.Print(LOG_DEBUG1, '[TUniverse] createEmpire - New Empire has been discovered #'+IntToStr(newEmpireID)+', Ruler: '+newRulerName);
  uniEmpire.Add( TEmpire.Create( newRulerName ) );

  theEmpireID := uniEmpire.Count-1;

  with uniEmpire[ theEmpireID ] do
  begin
    theStarID := uniStarList.getItem( newEmpireID ); // Transfer ownership of empires home system, to the empire.
    DevConsole.Print(LOG_DEBUG2, '[TUniverse] createEmpire - Using Star #'+IntToStr(theStarID));
    captureSolarSystem( theStarID, theEmpireID );
    createFleet( theStarID, theEmpireID );
  end;
end;
procedure TUniverse.tickerEmpires();
var E : integer;
begin
  for E := 0 to uniEmpire.Count-1 do
  begin
    with getEmpire( E ) do
    begin
      if (uniEmpireDead) then
      begin
        if (empireDead) then Continue;
        if (empireAstro.Count = 0) then empireDead := TRUE;
      end;

      empirePopulation    := 0;
      empireMaxPopulation := 0;
      empireTotalGrowth   := 0;
    end;
  end;
end;


//=============================
//========== Fleet
//=============================
function TUniverse.createFleet(newAstroID,theEmpireID:integer):integer;
begin
  //Do a loop here to find any used colonies. Only one colony per planet.
  result := uniFleet.Count;

  DevConsole.Print(LOG_DEBUG1, '[TUniverse] createFleet - '+IntToAlpha(theEmpireID)+' ('+IntToStr(theEmpireID)+') has created a new fleet at '+getAstro(newAstroID).astroName+' ('+IntToStr(newAstroID)+')');

  uniFleet.Add( TFleet.Create() );                    // Add a colony to the colony list
  getFleet( result ).linkEmpire( theEmpireID );        // also link that colony to an empire
  getFleet( result ).setVector( getAstro( newAstroID ).astroLocation, newAstroID );
  getFleet( result ).changePathMode();
  getFleet( result ).setSpeed(5000);

  getEmpire( theEmpireID ).linkFleet( result );        // Create a link from the Empire to the colony.
  getAstro( newAstroID ).fleetEnterOrbit();

  updateFleetMap();
end;
function TUniverse.fleetOnRoute(theAstroID,theEmpireID:integer):boolean;
//var F : integer;
begin
  result := FALSE;
  with getEmpire( theEmpireID ) do
  while empireFleet.getNextItem() do
  begin
    if (getFleet( empireFleet.currentItem() ).fleetEndID = theAstroID) then
    begin
     result := TRUE;
    end;
  end;
end;
function TUniverse.findTarget(theEmpireID,theFleetID:integer):integer;
//Look for a target system not controlled by the empire.
var S,A,X,Y, Counter, randomSystem, randomChance : integer;
begin
  result    := -1;

  Counter := 1+Random(uniStarList.Count-1);

  for S := 0 to Counter do // Fleets only travel between stars.
  begin
    randomSystem := Random(uniStarList.Count);

    A := uniStarList.getItem( randomSystem );
    with getAstro( A ) do
    begin
      if (astroEmpireID = -1) then
        randomChance := 75
      else
        randomChance := 25;

      if (astroEmpireID <> theEmpireID)and(not fleetOnRoute(A, theEmpireID ))and(random(100) < randomChance) then
      begin
        case getFleet( theFleetID ).fleetPathMode of
          pathHOME   : begin
            X := getAstro( uniStarList.getItem( theEmpireID ) ).astroLocation.X;
            Y := getAstro( uniStarList.getItem( theEmpireID ) ).astroLocation.Y;
          end;
          else
          begin
            X := getFleet( theFleetID ).fleetCurrentVector.X;
            Y := getFleet( theFleetID ).fleetCurrentVector.Y;
          end;
        end;

        if (result = -1)or
           (getAstro( A ).astroLocation.getDistance2D( X, Y ) <
            getAstro( result ).astroLocation.getDistance2D( X, Y )) then
        begin
          result      := A;
        end;
      end;
    end;
  end;

  if (result = -1) then
    DevConsole.Print( LOG_DEBUG2, '[TUniverse] findTarget - Empire '+IntToAlpha(theEmpireID)+' ('+IntToStr(theEmpireID)+') did not find a target')
  else
    DevConsole.Print( LOG_DEBUG2, '[TUniverse] findTarget - Empire '+IntToAlpha(theEmpireID)+' ('+IntToStr(theEmpireID)+') found new target '+getAstro(result).astroName+' ('+IntToStr(result)+')');
end;
procedure TUniverse.tickerFleetMove();
var F,newAttackID : integer;
begin
  for F := 0 to uniFleet.Count-1 do
  begin
    with getFleet( F ) do
    begin
      if (getEmpire( fleetEmpireID ).empireDead)and(fleetActive) then
      begin
        fleetActive := FALSE;
        getAstro( fleetStartID ).fleetLeaveOrbit();
      end;

      if (not fleetActive) then continue;

      if ( fleetIsMoving() ) then
      begin
        moveFleet( uniCurrentWeek );
        updateFleetMap();
      end
      else
      if ( fleetInOrbit() ) then
      begin
        if (random(100) < 15) then // Test for an attack
        begin
//          if (fleetStartID > -1) then
//            if (getColony( getAstro( fleetStartID ).astroColonyID ).colonyPopulation < 90) then Continue;

          try
            newAttackID := findTarget( fleetEmpireID, F);//  uniStarList.Items[ Random( uniStarList.Count ) ];
          except
            DevConsole.Print( LOG_CRITICAL, '[TUniverse] findTarget - Error in empire Value ('+IntToStr(fleetEmpireID)+') from fleet ('+IntToStr(F)+')');
            newAttackID := -1;
          end;

          if (newAttackID > -1) then
          begin
            launchFleet( getAstro( newAttackID ).astroLocation, newAttackID, uniCurrentWeek );
            if (fleetStartID <> fleetEndID) then
              getAstro( fleetStartID ).fleetLeaveOrbit();
          end;
        end;
        updateColonyMap(); // This is to update when a fleet sets it self into orbit.
        updateFleetMap(); // This is to update when a fleet sets it self into orbit.
      end
      else
      if ( fleetHasArrived() ) then
      begin
        DevConsole.Print(LOG_DEBUG2, '[TUniverse] tickerFleet - A fleet from '+IntToAlpha(fleetEmpireID)+'('+IntToStr(fleetEmpireID)+') has arrived at '+getAstro(fleetEndID).astroName+' ('+IntToStr(fleetEndID)+')');

        if (fleetStartID <> fleetEndID) then
          getAstro( fleetEndID ).fleetEnterOrbit();

        captureSolarSystem( fleetEndID, fleetEmpireID );

        if (getAstro(fleetEndID).astroEmpireID = -1) then
          Inc( fleetXP, 3 )
        else
          Inc( fleetXP, 5 );

        if (fleetXP > (100*getEmpire( fleetEmpireID ).empireFleet.Count) ) then
        begin
          if (fleetSpeed < 100) then // give the parent a speed boost
            Inc( fleetSpeed, fleetSpeed*2);

          newAttackID := uniStarList.getItem( fleetEmpireID ); // Should create new fleet at home system.
          createFleet( newAttackID, fleetEmpireID );

          if (Random(100) < 4) then
            createFleet( newAttackID, fleetEmpireID );  // Bonus double fleet

          fleetXP := 0;
        end;

        setEndPoint();
        fleetStatus := fleetORBIT;

        updateColonyMap(); // This is to update when a fleet sets it self into orbit.
        updateFleetMap();
      end;
    end;
  end;
end;
procedure TUniverse.tickerFleetCombat();
var F : integer;
begin
  for F := 0 to uniFleet.Count-1 do
  begin
    with getFleet( F ) do
    begin
      if (getEmpire( fleetEmpireID ).empireDead) then continue;

      if (fleetInOrbit()) then
      begin
        { theAttackersWon := TRUE }
        if (getAstro( fleetEndID ).astroFleetsInOrbit > 0) then //Must tackle the hostile fleets first
        begin
          { theAttackersWon := }tickerFleetFight( findOrbittingFleets( fleetEndID ) );
        end;

        {if theAttackersWon then}
        {  attackPlanets() }
      end;
    end;
  end;
end;
procedure TUniverse.tickerFleetFight(fleetsInOrbit:TIntegerArray);
begin
{
1) fleet v fleet
2) fleet v planet
3) planet v fleet
}
end;
procedure TUniverse.tickerFleets();
begin
  tickerFleetMove();
//  tickerFleetCombat();
end;

//=============================
//========== Simpler Typecasting Functions
//=============================
function TUniverse.getAstro(theAstroID:integer):TAstro;
begin
  if (theAstroID < uniAstro.Count)  then
    result := TAstro( uniAstro.Items[ theAstroID ] )
  else
    result := nil;
end;
function TUniverse.getStarLane(theStarLaneID:integer):TStarLane;
begin
  if (theStarLaneID < uniStarLane.Count)  then
    result := TStarLane( uniStarLane.Items[ theStarLaneID ] )
  else
    result := nil;
end;
function TUniverse.getEmpire(theEmpireID:integer):TEmpire;
begin
  if (theEmpireID < uniEmpire.Count)  then
    result := TEmpire( uniEmpire.Items[ theEmpireID ] )
  else
    result := nil;
end;
function TUniverse.getFleet(theFleetID:integer):TFleet;
begin
  if (theFleetID < uniFleet.Count)  then
    result := TFleet( uniFleet.Items[ theFleetID ] )
  else
    result := nil;
end;

//=============================
//========== Ticker
//=============================
procedure TUniverse.Ticker();
begin
  updateStarmap( FALSE ); //this may conflict with this being set outside this procedure.
  updateColonymap( FALSE ); //this may conflict with this being set outside this procedure.
  updateFleetmap( FALSE ); //this may conflict with this being set outside this procedure.

  Inc( uniCurrentWeek );

  DevConsole.Print( LOG_DEBUG1, '[TUniverse] ');
  DevConsole.Print( LOG_DEBUG1, '[TUniverse] >>>> '+IntToStr(uniCurrentWeek)+' <<<<');
  DevConsole.Print( LOG_DEBUG1, '[TUniverse] ');

  tickerFleets();
  tickerEmpires();
  tickerAstro();

  DevConsole.Print( LOG_DEBUG1, '[TUniverse.Ticker] Empires: '+IntToStr(uniEmpire.Count)+', Astros: '+IntToStr(uniAstro.Count)+', Fleets: '+IntToStr(uniFleet.Count)+', Ships: '+IntToStr(uniShip.Count)+' ');
end;

procedure TUniverse.exportInfo();
var E,C  : integer;
begin
  DevConsole.Print( LOG_DEBUG1, '[Stats Export] Empires: '+IntToStr(uniEmpire.Count)+', Astros: '+IntToStr(uniAstro.Count)+', Fleets: '+IntToStr(uniFleet.Count)+', Ships: '+IntToStr(uniShip.Count)+' ');
  for E := 0 to uniEmpire.Count-1 do
  begin
    with getEmpire( E ) do
    begin
      DevConsole.Print( LOG_DEBUG2, '[Stats Export] Astro Links: '+IntToStr(empireAstro.Count)+', Fleet Links: '+IntToStr(empireFleet.Count)+' Ship Links: '+IntToStr(empireShip.Count));

      if (empireAstro.Count > 0) then
      for C := 0 to empireAstro.Count-1 do
      begin
        DevConsole.Print( LOG_DEBUG3, '[Stats Export] -Astro Link: '+IntToStr(empireAstro.getItem(C)) );
      end;
    end;
  end;
end;

procedure TUniverse.updateStarMap(theValue:boolean=TRUE);
begin
  m_updateStarMap := theValue;
end;
procedure TUniverse.updateColonyMap(theValue:boolean=TRUE);
begin
  m_updateColonyMap := theValue;
end;
procedure TUniverse.updateFleetMap(theValue:boolean=TRUE);
begin
  m_updateFleetMap := theValue;
end;
procedure TUniverse.updateAllMaps(theValue:boolean=TRUE);
begin
  m_updateAllMaps := theValue;
end;

end.
